#pragma once

#include "dbc/structure.h"
#include "map/griddefines.h"
#include "object/creature.h"

class MapPersistentState;
struct PoolTemplateData
{
    PoolTemplateData() : mapEntry(NULL), MaxLimit(0), AutoSpawn(false) {}

    dbc::MapEntry const* mapEntry;                               // Map id used for pool creature/gameobject spams. In case non-instanceable map
    // it can be not unique but base at sharing same pool system dynamic data in this case this is not important.
    // NULL is no spawns by some reason
    unsigned int  MaxLimit;
    bool AutoSpawn;                                         // spawn at pool system start (not part of another pool and not part of event spawn)
    std::string description;

    // helpers
    bool CanBeSpawnedAtMap(dbc::MapEntry const* entry) const
    {
        return mapEntry && (mapEntry == entry || (!entry->Instanceable() && !mapEntry->Instanceable()));
    }
};

struct PoolObject
{
    unsigned int  guid;
    float   chance;
    bool exclude;

    PoolObject(unsigned int _guid, float _chance) : guid(_guid), chance(fabs(_chance)), exclude(false) {}

    template<typename T>
    void CheckEventLinkAndReport(unsigned int poolId, short event_id, std::map<unsigned int, short> const& creature2event, std::map<unsigned int, short> const& go2event) const;
};

class Pool                                                  // for Pool of Pool case
{
};

typedef std::set<unsigned int> SpawnedPoolObjects;
typedef std::map<unsigned int, unsigned int> SpawnedPoolPools;

class SpawnedPoolData
{
public:
    SpawnedPoolData() : m_isInitialized(false) {}

    template<typename T>
    bool IsSpawnedObject(unsigned int db_guid_or_pool_id) const;

    unsigned int GetSpawnedObjects(unsigned int pool_id) const;

    template<typename T>
    void AddSpawn(unsigned int db_guid_or_pool_id, unsigned int pool_id);

    template<typename T>
    void RemoveSpawn(unsigned int db_guid_or_pool_id, unsigned int pool_id);

    bool IsInitialized() const { return m_isInitialized; }
    void SetInitialized() { m_isInitialized = true; }

    SpawnedPoolObjects const& GetSpawnedCreatures() const { return mSpawnedCreatures; }
    SpawnedPoolObjects const& GetSpawnedGameobjects() const { return mSpawnedGameobjects; }
    SpawnedPoolPools const& GetSpawnedPools() const { return mSpawnedPools; }
private:
    SpawnedPoolObjects mSpawnedCreatures;
    SpawnedPoolObjects mSpawnedGameobjects;
    SpawnedPoolPools   mSpawnedPools;
    bool m_isInitialized;
};

typedef std::vector<PoolObject> PoolObjectList;

template <class T>
class PoolGroup
{
public:
    explicit PoolGroup() : poolId(0) {}
    void SetPoolId(unsigned int pool_id) { poolId = pool_id; }
    ~PoolGroup() {};
    bool isEmpty() const { return ExplicitlyChanced.empty() && EqualChanced.empty(); }
    void AddEntry(PoolObject& poolitem, unsigned int maxentries);
    bool CheckPool() const;
    void CheckEventLinkAndReport(unsigned short event_id, std::map<unsigned int, short> const& creature2event, std::map<unsigned int, short> const& go2event) const;
    PoolObject* RollOne(SpawnedPoolData& spawns, unsigned int triggerFrom);
    void DespawnObject(MapPersistentState& mapState, unsigned int guid = 0);
    void Despawn1Object(MapPersistentState& mapState, unsigned int guid);
    void SpawnObject(MapPersistentState& mapState, unsigned int limit, unsigned int triggerFrom, bool instantly);
    void SetExcludeObject(unsigned int guid, bool state);

    void Spawn1Object(MapPersistentState& mapState, PoolObject* obj, bool instantly);
    void ReSpawn1Object(MapPersistentState& mapState, PoolObject* obj);
    void RemoveOneRelation(unsigned short child_pool_id);

    PoolObjectList const& GetExplicitlyChanced() const { return ExplicitlyChanced; }
    PoolObjectList const& GetEqualChanced() const { return EqualChanced; }

    size_t size() const { return ExplicitlyChanced.size() + EqualChanced.size(); }
private:
    unsigned int poolId;
    PoolObjectList ExplicitlyChanced;
    PoolObjectList EqualChanced;
};

class PoolManager : public cftf::util::Singleton<PoolManager>
{
    friend class cftf::util::Singleton<PoolManager>;
    PoolManager();
public:
    ~PoolManager() {};

    void LoadFromDB();
    void Initialize(MapPersistentState* state);         // called at new MapPersistentState object create

    unsigned short GetMaxPoolId() const { return max_pool_id; }

    template<typename T>
    unsigned short IsPartOfAPool(unsigned int db_guid_or_pool_id) const;

    // Method that tell if the creature/gameobject/pool is part of top level pool and return the pool id if yes
    template<typename T>
    unsigned short IsPartOfTopPool(unsigned int db_guid_or_pool_id) const
    {
        if (unsigned short pool_id = IsPartOfAPool<T>(db_guid_or_pool_id))
        {
            if (unsigned short top_pool_id = IsPartOfTopPool<Pool>(pool_id))
            {
                return top_pool_id;
            }

            return pool_id;
        }

        return 0;
    }

    template<typename T>
    void SetExcludeObject(unsigned short pool_id, unsigned int db_guid_or_pool_id, bool state);

    bool CheckPool(unsigned short pool_id) const;
    void CheckEventLinkAndReport(unsigned short pool_id, unsigned short event_id, std::map<unsigned int, short> const& creature2event, std::map<unsigned int, short> const& go2event) const;

    void SpawnPool(MapPersistentState& mapState, unsigned short pool_id, bool instantly);
    void DespawnPool(MapPersistentState& mapState, unsigned short pool_id);

    template<typename T>
    void UpdatePool(MapPersistentState& mapState, unsigned short pool_id, unsigned int db_guid_or_pool_id = 0);

    // used for calling from global systems when need spawn pool in all appropriate map instances
    void SpawnPoolInMaps(unsigned short pool_id, bool instantly);
    void DespawnPoolInMaps(unsigned short pool_id);

    // used for calling from global systems when need initialize spawn pool state in appropriate (possible) map persistent state
    void InitSpawnPool(MapPersistentState& mapState, unsigned short pool_id);

    template<typename T>
    void UpdatePoolInMaps(unsigned short pool_id, unsigned int db_guid_or_pool_id = 0);

    void RemoveAutoSpawnForPool(unsigned short pool_id) { mPoolTemplate[pool_id].AutoSpawn = false; }

    typedef std::vector<PoolTemplateData> PoolTemplateDataMap;
    PoolTemplateData const& GetPoolTemplate(unsigned short pool_id) const { return mPoolTemplate[pool_id]; }
    PoolGroup<battle::object::Creature> const& GetPoolCreatures(unsigned short pool_id) const { return mPoolCreatureGroups[pool_id]; }
    PoolGroup<battle::object::GameObject> const& GetPoolGameObjects(unsigned short pool_id) const { return mPoolGameobjectGroups[pool_id]; }
    PoolGroup<Pool> const& GetPoolPools(unsigned short pool_id) const { return mPoolPoolGroups[pool_id]; }
protected:
    template<typename T>
    void SpawnPoolGroup(MapPersistentState& mapState, unsigned short pool_id, unsigned int db_guid_or_pool_id, bool instantly);

    unsigned short max_pool_id;

    typedef std::vector<PoolGroup<battle::object::Creature> >   PoolGroupCreatureMap;
    typedef std::vector<PoolGroup<battle::object::GameObject> > PoolGroupGameObjectMap;
    typedef std::vector<PoolGroup<Pool> >       PoolGroupPoolMap;
    typedef std::pair<unsigned int, unsigned short> SearchPair;
    typedef std::map<unsigned int, unsigned short> SearchMap;

    PoolTemplateDataMap mPoolTemplate;
    PoolGroupCreatureMap mPoolCreatureGroups;
    PoolGroupGameObjectMap mPoolGameobjectGroups;
    PoolGroupPoolMap mPoolPoolGroups;

    // static maps DB low guid -> pool id
    SearchMap mCreatureSearchMap;
    SearchMap mGameobjectSearchMap;
    SearchMap mPoolSearchMap;
};

// Method that tell if the creature is part of a pool and return the pool id if yes
template<>
inline unsigned short PoolManager::IsPartOfAPool<battle::object::Creature>(unsigned int db_guid) const
{
    SearchMap::const_iterator itr = mCreatureSearchMap.find(db_guid);
    if (itr != mCreatureSearchMap.end())
    {
        return itr->second;
    }

    return 0;
}

// Method that tell if the gameobject is part of a pool and return the pool id if yes
template<>
inline unsigned short PoolManager::IsPartOfAPool<battle::object::GameObject>(unsigned int db_guid) const
{
    SearchMap::const_iterator itr = mGameobjectSearchMap.find(db_guid);
    if (itr != mGameobjectSearchMap.end())
    {
        return itr->second;
    }

    return 0;
}

// Method that tell if the pool is part of another pool and return the pool id if yes
template<>
inline unsigned short PoolManager::IsPartOfAPool<Pool>(unsigned int pool_id) const
{
    SearchMap::const_iterator itr = mPoolSearchMap.find(pool_id);
    if (itr != mPoolSearchMap.end())
    {
        return itr->second;
    }

    return 0;
}

